#include "QPCoder.h"

using namespace kc_sf;

void 
QPCoder::decode( const std::string &input, std::string &output )
{
    output.clear();
    if( input.empty() )     return;
    
    /* a.) znajduje pozycje znaków "=", czyli potencjalnych zakodowanych 
     *     znaków
     * b.) sprawdza znaki następujące po "=" i jesli są to zakodowane
     *     znaki dekoduje je
     * c.) przepisuje pozostale na wyjscie
     */
    std::vector<unsigned int> positions = findEqual(input);
    if( positions.empty() )
    {
        output += input;
        return;
    }
    
    unsigned int last_start = 0;
    
    for( unsigned int i = 0; i < positions.size() 
         && last_start < input.size(); 
         ++i )
    {
        output += input.substr( last_start, positions[i] - last_start );
        unsigned int next = positions[i];
        if( ++next < input.size() )
        {
            // soft line_break
            if( isCrlf( input[next] ) )
            {
                last_start = ommitCrlf( input, positions[i] );
            }
            
            // trailing context
            else if( isWsp( input[next] ) )
            {
                last_start = ommitTrailingContext( input, positions[i] );
            }
            
            // błąd w zakodowanym tekście - przepisywany taki jaki jest
            else if( !isHex( input[next] ) )
            {
                last_start = positions[i];
            }

            // zakodowany znak
            else
            {
                last_start = checkHex( input, positions[i], output );
            }
        }
    }
    
    // ostatni wyraz
    if( last_start < input.size() )
        output += input.substr( last_start, input.size() - last_start );
}    

// Pomocnicze ------------------------------------------------------- //

unsigned int
QPCoder::checkHex( const std::string &input, unsigned int start, std::string &output ) const
{
    unsigned int next = start, ret = start;
    ++ next;
    char first = input[next];       // pierwsza liczba
    if( ++ next < input.size() )
    {
    	char second = input[next];
        if( !isHex( second ) ) // błąd w zakodowanym tekście - przepisywany taki jaki jest
            return ret;
            
        unsigned int nr = hexToInt( first ) * 16 + 
                          hexToInt( second );  // cała liczba
        char nr_char = nr;                     // znak zapisany jako wartosc liczby

        // zmiana kodowania
		output += nr_char;
		ret += 3; // =xx
    }
    
    return ret;
}

unsigned int QPCoder::ommitCrlf( const std::string &input, unsigned int start ) const
{
    unsigned int ret = start, next = start;
    ++ next;
    
    // soft line_break
    if( isCrlf( input[next] ) )
    {
        ++ next;
        while( next < input.size() && isCrlf( input[next] ) )
            ++ next;

        ret = next;
    }
    
    return ret;
}            

unsigned int QPCoder::ommitTrailingContext( const std::string &input, unsigned int start ) const
{
    unsigned int ret = start, next = start;
    ++ next;

    // trailing context
    if( isWsp( input[next] ) )
    {
        ++ next;
        bool tr_context = false;
        while( next < input.size() && ( isWsp( input[next] ) ) )
        {
            if( isCrlf( input[next] ) )
            {
                tr_context = true;
                ++ next;
                break;
            }
            ++ next;
        }
        if( tr_context )
            ret = next;
    }

    return ret;
}

std::vector<unsigned int> QPCoder::findEqual(const std::string &input) const
{
    std::vector<unsigned int> positions;
    
    int pos = 0;
    while( pos < static_cast<int>( input.size() ) )
    {
        pos = input.find( '=', pos );
        
        if( pos == static_cast<int>( std::string::npos ) )
            break;
        
        positions.push_back( pos );
        ++ pos;
    }
    
    return positions;
}

// ------------------------------------------------------------------ //

bool QPCoder::isHex( char c ) const
{
    return ( c >= '0' && c <= '9' ) 
           || ( c >= 'A' && c <= 'F' )
           || ( c >= 'a' && c <= 'f' );
}

bool QPCoder::isWsp( char c ) const
{
    return ( c == ' ' || c == '\t' );
}

bool QPCoder::isCrlf( char c ) const
{
    return ( c == '\r' || c == '\n' );
}

unsigned int QPCoder::hexToInt( char c ) const
{
    if( c >= '0' && c <='9' )       return c - '0';
    else if( c >= 'A' && c <='F' )  return c - 'A' + 10;
    else if( c >= 'a' && c <='f' )  return c - 'a' + 10;
    else return 0;
}
